/* Copyright © Worklight Ltd. 2006-2011.  All rights reserved. */

package com.worklight.androidgap;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.json.JSONException;
import org.json.JSONObject;

import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.AssetManager;
import android.graphics.Color;
import android.os.Build;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.os.Environment;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.CookieManager;
import android.webkit.CookieSyncManager;
import android.webkit.WebSettings;
import android.webkit.WebSettings.LayoutAlgorithm;
import android.webkit.WebView;
import android.widget.LinearLayout;

import com.phonegap.DroidGap;
import com.phonegap.WebViewReflect;
import com.phonegap.api.LOG;
import com.worklight.androidgap.plugin.DirectUpdate;
import com.worklight.androidgap.plugin.NativePage;
import com.worklight.androidgap.plugin.WLMenuItem;
import com.worklight.androidgap.plugin.WLOptionsMenu;
import com.worklight.wlclient.WLConfig;

public class WLDroidGap extends DroidGap {	
	private static final String ENABLE_SETTINGS_FLAG = "enableSettings";
	public static final String WL_DEFAULT_SERVER_URL = "WLDefaultServerURL";
	public static final String EXIT_ON_SKIN_LOADER = "exitOnSkinLoader";
	public static final String SKIN_LOADER_HTML = "skinLoader.html";
	public static final String SKIN_NAME_PREF_KEY = "wlSkinName";
	
	private static final String CLEAR_CACHE_NEXT_LOAD = "com.worklight.clearCacheNextLoad";
	private static final String STORE_EXTERNAL = "com.worklight.storeExternal";
	private static boolean isForegound = false;
	
	private WLOptionsMenu optionsMenu = null;		
	private static WLConfig wlConfig;

	private static String EXTERNAL_PATH = null; 
	private static String INTERNAL_PATH = null;
	
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		init();
		new PrepackagedAssetsManager().copyPrepackagedAssetsToLocalCopyIfNeeded();
		wlConfig = new WLConfig(getApplication());
		// reset the backPressed pref - used as flag in skinLoader.html
		WLAndroidUtils.writeWLPref (this, WL_DEFAULT_SERVER_URL, getWLConfig().getBaseServerURL().toString());
		WLAndroidUtils.writeWLPref (this, EXIT_ON_SKIN_LOADER, "false");
		WLAndroidUtils.writeWLPref (this, ENABLE_SETTINGS_FLAG, getWLConfig().getSettingsFlag().toString());
		appView.setBackgroundColor(Color.BLACK);
		setForeGround (true);
	}
	
	@Override
	public void init() {
		// Fix bug for ING, were animation is play slowly because of HTC desire 2.1 bug
		if (Build.VERSION.SDK_INT == VERSION_CODES.ECLAIR_MR1 && Build.BRAND.equalsIgnoreCase("htc")) {
			initForHTC21 ();
		} else {
			super.init();
		}	
    }
	
	/**
	 * When upgrade PhoneGap this function should replace, but the WLWebView should stay 
	 */
	private void initForHTC21 () {
		LOG.d(TAG, "DroidGap.init()");
        
        // Create web container
        this.appView = new WLWebView(this);
        this.appView.setId(100);
        
        this.appView.setLayoutParams(new LinearLayout.LayoutParams(
                ViewGroup.LayoutParams.FILL_PARENT,
                ViewGroup.LayoutParams.FILL_PARENT, 
                1.0F));

        WebViewReflect.checkCompatibility();

        this.appView.setWebChromeClient(new GapClient(this));
        this.setWebViewClient(this.appView, new GapViewClient(this));

        this.appView.setInitialScale(100);
        this.appView.setVerticalScrollBarEnabled(false);
        this.appView.requestFocusFromTouch();

        // Enable JavaScript
        WebSettings settings = this.appView.getSettings();
        settings.setJavaScriptEnabled(true);
        settings.setJavaScriptCanOpenWindowsAutomatically(true);
        settings.setLayoutAlgorithm(LayoutAlgorithm.NORMAL);
        
        //Set the nav dump for HTC
        settings.setNavDump(true);

        // Enable database
        settings.setDatabaseEnabled(true);
        String databasePath = this.getApplicationContext().getDir("database", Context.MODE_PRIVATE).getPath(); 
        settings.setDatabasePath(databasePath);

        // Enable DOM storage
        WebViewReflect.setDomStorage(settings);
        
        // Enable built-in geolocation
        WebViewReflect.setGeolocationEnabled(settings, true);

        // Add web view but make it invisible while loading URL
        this.appView.setVisibility(View.INVISIBLE);
        root.addView(this.appView);
        setContentView(root);
        
        // Clear cancel flag
        this.cancelLoadUrl = false;
	}

	@Override
	public void onResume () {
		super.onResume();
		setForeGround (true);		
	}
	
	@Override
	public void onPause () {
		super.onPause();
		setForeGround (false);
	}
	
	public static boolean isForeGround () {
		return isForegound;
	}
	
	private static void setForeGround (boolean foreground) {
		isForegound = foreground;
	}
	
	/* Creates the menu items */
	public boolean onCreateOptionsMenu(Menu menu) {    	
		return onPrepareOptionsMenu(menu);
	}

	public boolean onPrepareOptionsMenu(Menu menu){
		if (! optionsMenu.isInit() || ! optionsMenu.hasChanged()){
			return true;
		}
		menu.clear();

		if (optionsMenu.isVisible()){
			List<WLMenuItem> items  = optionsMenu.getItems();
			for (WLMenuItem button : items){
				MenuItem item = menu.add(0, button.getId(), 0, button.getTitle());	    		
				item.setEnabled(button.isEnabled() && optionsMenu.isEnabled());    	
				if (button.getImagePath() != null){
					item.setIcon(button.getImage(this, optionsMenu.getSkinName()));
				}
			}
			optionsMenu.unsetChanged();
		}

		return true;
	}

	/* Handles item selections */
	public boolean onOptionsItemSelected(MenuItem item) {		
		WLMenuItem btn = optionsMenu.getItemById(item.getItemId());
		if (btn != null){
			sendJavascript(btn.getCallback());
			return true;
		}		
		return false;		
	}

	protected void bindBrowser(WebView appView) {	
		this.addService("Logger", 				"com.worklight.androidgap.plugin.Logger");
		this.addService("Utils",  				"com.worklight.androidgap.plugin.Utils");
		this.addService("NativePage",   		"com.worklight.androidgap.plugin.NativePage");
		this.addService("NativeBusyIndicator",  "com.worklight.androidgap.plugin.BusyIndicator");
		this.addService("Push", 				"com.worklight.androidgap.plugin.Push");
		this.addService("DirectUpdate",			"com.worklight.androidgap.plugin.DirectUpdate");
		this.addService("NetworkDetector",		"com.worklight.androidgap.plugin.NetworkDetector");
		optionsMenu = new WLOptionsMenu(this);		
		appView.addJavascriptInterface(optionsMenu, "NativeOptionsMenu");
		removeSessionCookies(appView);
	}

	private void removeSessionCookies(WebView appView) {
		CookieSyncManager.createInstance(appView.getContext());
		CookieManager.getInstance().removeSessionCookie();
	}

	@Override
	protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
		if (requestCode == NativePage.NATIVE_ACTIVITY_REQ_CODE) {
			if (resultCode == RESULT_OK) {
				WLAndroidUtils.debug("NativePage returned OK result code");
			} else {
				WLAndroidUtils.error("NativePage returned result code " + resultCode);
			}
			String javascript = "WL.NativePage.onNativePageClose("+ getIntentDataInJSONFormat(intent) +")";
			sendJavascript(javascript);
		} else {
			super.onActivityResult(requestCode, resultCode, intent);
		}
	}

  	/**
  	 * Because PhoneGap override this method we need to override it to let native options menu works
  	 */
	@Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
		if (keyCode ==  KeyEvent.KEYCODE_BACK && DirectUpdate.isUpdating()) {
			return false;
		}
	  	boolean returnValue = super.onKeyDown(keyCode, event);
	  	if (keyCode == KeyEvent.KEYCODE_MENU) {
            return false;
        } else {
        	return returnValue;
        }
    }
	
  	
	/**
	 * Convert Intet extras to JSON
	 * @param data - the intent that hold the data
	 * @return
	 */
	protected String getIntentDataInJSONFormat(Intent intent) {
		String json = "";
		if (intent != null && intent.getExtras() != null) {
			Set<String> dataKey = intent.getExtras().keySet();
			Map<String, Object> extrasMap = new HashMap<String, Object>();
			for (String key : dataKey) {
				extrasMap.put(key, intent.getExtras().get(key));
			}
			JSONObject extrasJson = new JSONObject(extrasMap);
			json = extrasJson.toString();
		}
		return json;
	}

	// the solution based on http://stackoverflow.com/questions/2042222/android-close-application
	@Override
	public void onDestroy() {
	    super.onDestroy();

	    /*
	     * Notify the system to finalize and collect all objects of the
	     * application on exit so that the process running the application can
	     * be killed by the system without causing issues. NOTE: If this is set
	     * to true then the process will not be killed until all of its threads
	     * have closed.
	     */
	    System.runFinalizersOnExit(true);

	    /*
	     * Force the system to close the application down completely instead of
	     * retaining it in the background. The process that runs the application
	     * will be killed. The application will be completely created as a new
	     * application in a new process if the user starts the application
	     * again.
	     */
	    System.exit(0);
	}
	
	public static WLConfig getWLConfig () {
		return wlConfig;
	}
	
	public void loadUrl(String url){
		if (isClearCacheNextLoad()){
			appView.clearCache(true);
			setClearCacheNextLoad(false);			
		}
		super.loadUrl(url);
		bindBrowser(appView);
	}
	
	public void setClearCacheNextLoad(){
		setClearCacheNextLoad(true);
	}
	
	private void setClearCacheNextLoad(boolean clearCacheNextLoad){
		SharedPreferences.Editor editor = getPreferences(Context.MODE_PRIVATE).edit();
		editor.putBoolean(CLEAR_CACHE_NEXT_LOAD, clearCacheNextLoad);
		editor.commit();
	}
	
	private boolean isClearCacheNextLoad(){
		return getPreferences(Context.MODE_PRIVATE).getBoolean(CLEAR_CACHE_NEXT_LOAD, false);
	}
	
	/**
	 * Store flag to know if storage is external or internal
	 * @param isExternal
	 */
	public void setStoreExternal(boolean isExternal){
		SharedPreferences.Editor editor = getPreferences(Context.MODE_PRIVATE).edit();
		editor.putBoolean(STORE_EXTERNAL, isExternal);
		editor.commit();
	}
	
	/**
	 * Return the store external flag
	 * @param isExternal
	 */
	private boolean isStoreExternal(){
		return getPreferences(Context.MODE_PRIVATE).getBoolean(STORE_EXTERNAL, false);
	}
	
	///////////////////////////
	// Useful Paths Getters
	///////////////////////////
	
	public String getLocalStorageRoot() {
		if (isStoreExternal()) {
			return getExternalPath();
		} else {
			return getInternalPath ();
		}
	}

	private String getExternalPath() {
		if (EXTERNAL_PATH == null) {
			EXTERNAL_PATH = Environment.getExternalStorageDirectory() + "/" + this.getPackageName() + "." + this.getPackageManager().getApplicationLabel(getApplicationInfo()).toString().replaceAll("\\s","");
		}
		return EXTERNAL_PATH;
	}
	
	private String getInternalPath() {
		if (INTERNAL_PATH == null) {
			INTERNAL_PATH = getApplicationContext().getFilesDir().getAbsolutePath();
		}
		return INTERNAL_PATH;
	}
	
	public String getLocalStorageWebRoot(){
		return getLocalStorageRoot() + "/www";
	}
	
	public String getWebMainFilePath(){
		String mainFilePath = null;
		String skinName = WLAndroidUtils.readWLPref(this, SKIN_NAME_PREF_KEY);
		if (skinName != null){
			mainFilePath = getAppWebUrl(skinName);			
		} else {
			mainFilePath = getWebUrl() + "/" + SKIN_LOADER_HTML; 			
		}
		return mainFilePath; 
	}
	
	public String getWebUrl() {
		return "file://" + getLocalStorageWebRoot();
	}
	
	public String getAppWebUrl(String skinName){
		return getWebUrl() + "/" + skinName + "/" + getWLConfig().getMainFilePath();
	}
	
	/**
	 * Test if device is lagging copying files to internal file
	 * lagging means copy one file in more then 100 ms
	 * @param assetManager
	 * @return
	 */
	public boolean isDeviceHasLagging (AssetManager assetManager) {
		Date d1 = new Date();
		testCopyOneFile(assetManager, "www/default/common/js/base.js", getApplicationContext().getFilesDir().getAbsolutePath());	
		Date d2 = new Date();
		long timeTookeToCopyFile = d2.getTime() - d1.getTime();
		WLAndroidUtils.debug("Check copy 1 file => " + timeTookeToCopyFile + " ms");
		return timeTookeToCopyFile > 100;
	}
		
	/**
	 * Copy one file to check FS copy speed (some device has lagging problems, if so copy to SD)
	 * @param assetManager
	 * @param relativePath
	 * @param targetRootPath
	 */
	private void testCopyOneFile (AssetManager assetManager, String relativePath, String targetRootPath) {
		InputStream in = null;
		OutputStream out = null;
		File targetFile = new File(targetRootPath + "/" + relativePath);
		try {				
			targetFile.getParentFile().mkdirs();
			in = assetManager.open(relativePath);
			out = new FileOutputStream(targetFile);
			WLAndroidUtils.copyFile(in, out);				
		} catch (Exception e) {
			throw new RuntimeException("Fail test copy on file: " + targetFile.getAbsoluteFile(), e);				
		} finally {
			try {
				if (in != null){
					in.close();
				}
			} catch (IOException e) {
				WLAndroidUtils.error("Failed close file input stream: " + relativePath + ". " + e.toString());
			}
			try {
				if (out != null) {
					out.close();
				}
			} catch (IOException e) {
				WLAndroidUtils.error("Failed close file output stream: " + targetFile.getAbsolutePath() + ". " + e.toString());
			}
		}
	}
	
	////////////////////////////////////////
	// PrepackagedAssetsManager Inner Class
	////////////////////////////////////////

	public class PrepackagedAssetsManager {
		
		private void copyPrepackagedAssetsToLocalCopyIfNeeded(){
			if (isNewPackage()){
				copyPrepackagedAssetsToLocalCopy();
			}
		} 
		
		private boolean isNewPackage() {
			boolean result = false; 
			try {
				String APP_INSTALL_TIME_KEY = "appInstallTime";
				PackageManager pm = getPackageManager();
				ApplicationInfo appInfo = pm.getApplicationInfo(getPackageName(), 0);
				String appFile = appInfo.sourceDir;
				long appInstallTime = new File(appFile).lastModified();
				SharedPreferences preferences = getPreferences(MODE_PRIVATE);
				long lastAppInstallTime = preferences.getLong(APP_INSTALL_TIME_KEY, 0);
				
				if (appInstallTime > lastAppInstallTime){
					Editor editor = preferences.edit();
					editor.putLong(APP_INSTALL_TIME_KEY, appInstallTime);
					editor.commit();
					result = true;					
				}				
			} catch (NameNotFoundException e) {
				throw new RuntimeException("PrepackagedAssetsManager Failure. Can't retrieve ApplicationInfo - something fundumental went wrong here.", e);
			}
			return result;
		}

		// copy the pre-packaged assets to the local copy
		private void copyPrepackagedAssetsToLocalCopy(){
			AssetManager assetManager = WLDroidGap.this.getApplicationContext().getAssets();
			if (isDeviceHasLagging(assetManager) && Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
				setStoreExternal(true);
			}
			WLAndroidUtils.deleteDirectory(new File(getLocalStorageWebRoot()));
			WLAndroidUtils.debug("Start copy files to local storage...");
			copyPrepackagedAssetsToLocalCopy(assetManager, "www", getLocalStorageRoot());				
			copyPrepackagedAssetsToLocalCopy(assetManager, WLConfig.WL_CLIENT_PROPS_NAME, getLocalStorageWebRoot());
			WLAndroidUtils.debug("Finish copy files to local storage...");
		}
		
		private void copyPrepackagedAssetsToLocalCopy(AssetManager assetManager, String relativePath, String targetRootPath) {
			InputStream in = null;
			OutputStream out = null;
			File targetFile = new File(targetRootPath + "/" + relativePath);
			try {				
				
				// Open the pre-packaged file or folder.
				// Note: At the moment AssetManager doesn't expose any method to distinguish between file and directory.
				// So, as a workaround, we use the IOException mechanism to distinguish between the two:
				// If it's a directory - when we try opening the asset, an IOException is thrown.
				// (see http://stackoverflow.com/questions/3010157/determining-filetype-of-file-in-assets-folder)
				
				// if source asset is a file
				try {
					in = assetManager.open(relativePath);
					out = new FileOutputStream(targetFile);
					WLAndroidUtils.copyFile(in, out);
				} 
				
				// if source asset is a directory
				catch (IOException e) {
					targetFile.mkdirs();
					String[] files = assetManager.list(relativePath);					
					for (int i = 0; i < files.length; i++) {
						copyPrepackagedAssetsToLocalCopy(assetManager, relativePath + "/" + files[i], targetRootPath);
					}
				}				
			} catch (Exception e) {
				throw new RuntimeException("PrepackagedAssetsManager Failure. Failed copying prepackaged assets to local storage. The exception occured when copying the following file/directory: " + targetFile.getAbsoluteFile(), e);				
			} finally {
				try {
					if (in != null){
						in.close();
					}
				} catch (IOException e) {
					WLAndroidUtils.error("Failed close file input stream: " + relativePath + ". " + e.toString());
				}
				try {
					if (out != null) {
						out.close();
					}
				} catch (IOException e) {
					WLAndroidUtils.error("Failed close file output stream: " + targetFile.getAbsolutePath() + ". " + e.toString());
				}
			}
		}

		class Checksum implements Comparable<Checksum>{
			private String checksum;
			private Long date;
			private String machine;

			public Checksum(JSONObject jsonObject) throws JSONException {
				super();
				this.checksum = jsonObject.getString("checksum");
				this.date = jsonObject.getLong("date");
				this.machine = jsonObject.getString("machine");
			}

			public String getChecksum() {
				return checksum;
			}

			public Long getDate() {
				return date;
			}

			public String getMachine() {
				return machine;
			}

			@Override
			// if the checksums are different -> the comparison will be determined by the dates.
			public int compareTo(Checksum another) {
				if (this.checksum.compareTo(another.checksum) != 0){
					return this.date.compareTo(another.date);
				} else {
					return 0;
				}				 
			}
		}
	}
}
